<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>智能摄像头</title>
    <!-- 使用jsDelivr CDN加载mpegts.js -->
    <script src="./mpegts.js"></script>
</head>
<body>
    <div style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #333; display: flex; justify-content: center; align-items: center;">
        <video id="videoElement" controls muted autoplay style="width: 100%; height: 100%; object-fit: contain;"></video>
        <canvas id="roiCanvas" style="position: absolute; pointer-events: none; z-index: 1;"></canvas>
        <button id="captureBtn" style="position: fixed; bottom: 100px; right: 20px; padding: 15px 30px; background: #2196F3; color: white; border: none; border-radius: 8px; cursor: pointer; z-index: 2; font-size: 16px; touch-action: none;">拍照</button>
        <button id="updateRoiBtn" style="position: fixed; bottom: 20px; right: 20px; padding: 15px 30px; background: #4CAF50; color: white; border: none; border-radius: 8px; cursor: pointer; z-index: 2; font-size: 16px; touch-action: none;">更新识别区域</button>
    </div>
    <div id="stats" style="position: fixed; top: 10px; right: 10px; background: rgba(0,0,0,0.7); color: white; padding: 8px; border-radius: 4px;">
        <div>ROI位置: <span id="roiPosition">0,0</span></div>
    </div>

    <script>
        // 低延迟配置参数
        const config = {
            type: 'flv',
            url: '/live/test.live.flv',
            isLive: true,
            isLowLatency: true,
            lazyLoad: false,
            enableWorker: true,
            enableStashBuffer: false,
            stashInitialSize: 128,
            autoCleanupSourceBuffer: true,
            withCredentials: false,
            hasAudio: false,
            liveBufferLatencyChasing: true,
            seekType: 'range',
            // 新增后台处理配置
            liveSync: true, // 强制同步直播时间
            liveSyncMaxLatency: 0.5, // 最大允许延迟（秒）
            livePlaybackRate: 1.1 // 轻微加速追赶实时流
        };

        let player = null;
        let reconnectAttempts = 0;
        const maxReconnectAttempts = 5;
        let isPageVisible = true;

        // 页面可见性检测
        function handleVisibilityChange() {
            isPageVisible = !document.hidden;
            if (player) {
                if (isPageVisible) {
                    // 页面可见时恢复播放并重置延迟
                    player.unload();
                    player.load();
                    player.play();
                    // 通过重新加载播放器强制同步最新帧
                    player.unload();
                    player.load();
                    player.play();
                } else {
                    // 页面不可见时暂停并清空缓冲区
                    player.pause();
                    player.unload();
                }
            }
        }

        // 优化后的播放器初始化
        function initPlayer() {
            if (mpegts.isSupported()) {
                const videoElement = document.getElementById('videoElement');
                
                // 创建低延迟播放器
                player = mpegts.createPlayer(config);
                player.attachMediaElement(videoElement);
                player.load();
                player.play();

                // 注册页面可见性监听
                document.addEventListener('visibilitychange', handleVisibilityChange);
                document.addEventListener('resume', handleVisibilityChange); // 兼容某些移动端浏览器

                // 错误处理与重连机制
                player.on(mpegts.Events.ERROR, (err) => {
                    console.error('播放错误:', err);
                    // 仅在页面可见时尝试重连
                    if (isPageVisible && reconnectAttempts < maxReconnectAttempts) {
                        setTimeout(() => {
                            player.destroy();
                            initPlayer();
                            reconnectAttempts++;
                        }, 1000); // 缩短重连间隔
                    } else {
                        alert('连接失败，已尝试重连' + maxReconnectAttempts + '次');
                    }
                });

                // 网络中断检测
                window.addEventListener('offline', () => {
                    player.pause();
                    alert('网络连接已断开');
                });

                window.addEventListener('online', () => {
                    player.play();
                });
            } else {
                alert('当前浏览器不支持FLV直播播放');
            }
        }

        // 拍照按钮点击处理
        document.getElementById('captureBtn').addEventListener('click', async () => {
            try {
                const response = await fetch('/capture', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                });
                const result = await response.json();
                // alert(result.status);
            } catch (error) {
                console.error('拍照失败:', error);
                alert('拍照请求失败');
            }
        });

        // 区域选择功能
        class ROISelector {
            constructor() {
                this.canvas = document.getElementById('roiCanvas');
                this.ctx = this.canvas.getContext('2d');
                this.video = document.getElementById('videoElement');
                this.isDragging = false;
                this.dragType = null;
                this.selectedCorner = -1;
                this.points = [];
                this.threshold = 8;
            }

            async initialize() {
                await this.initDefaultRect();
                this.updateCanvasSize();
                this.setupEventListeners();
                this.draw(); // 确保数据加载后立即绘制
            }

            setupEventListeners() {
            this.canvas.style.pointerEvents = 'auto';
            // 鼠标事件
            this.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
            document.addEventListener('mousemove', this.handleMouseMove.bind(this));
            document.addEventListener('mouseup', this.handleMouseUp.bind(this));
            
            // 触摸事件
            this.canvas.addEventListener('touchstart', this.handleTouchStart.bind(this), {passive: false});
            document.addEventListener('touchmove', this.handleTouchMove.bind(this), {passive: false});
            document.addEventListener('touchend', this.handleTouchEnd.bind(this));
            
            window.addEventListener('resize', this.updateCanvasSize.bind(this));
        }

        // 触摸事件处理
        handleTouchStart(event) {
            event.preventDefault();
            if(event.touches.length === 1) {
                const touch = event.touches[0];
                const mouseEvent = new MouseEvent('mousedown', {
                    clientX: touch.clientX,
                    clientY: touch.clientY
                });
                this.handleMouseDown(mouseEvent);
            }
        }

        handleTouchMove(event) {
            event.preventDefault();
            if(event.touches.length === 1) {
                const touch = event.touches[0];
                const mouseEvent = new MouseEvent('mousemove', {
                    clientX: touch.clientX,
                    clientY: touch.clientY
                });
                this.handleMouseMove(mouseEvent);
            }
        }

        handleTouchEnd(event) {
            event.preventDefault();
            const mouseEvent = new MouseEvent('mouseup');
            this.handleMouseUp(mouseEvent);
        }

            async initDefaultRect() {
                try {
                    const response = await fetch('/riocfg', {
                        method: 'GET'
                    });
                    const result = await response.json();
                    
                    if(result.code === 0 ) {
                        this.points = result.roi_points.map(p => ({
                            x: parseFloat(p.x),
                            y: parseFloat(p.y)
                        }));
                    } else {
                        console.error('接口数据异常:', {
                            code: result.code,
                            data: result.data,
                            expected: 'data.roi_points[4]'
                        });
                        this.setDefaultPoints();
                    }
                } catch (error) {
                    console.error('获取ROI数据失败:', error);
                    this.setDefaultPoints();
                }
            }

            setDefaultPoints() {
                this.points = [
                    {x: 0.25, y: 0.25}, // 左上
                    {x: 0.75, y: 0.25}, // 右上
                    {x: 0.75, y: 0.75}, // 右下
                    {x: 0.25, y: 0.75}  // 左下
                ];
            }

            updateCanvasSize() {
                const rect = this.getVideoDisplayRect();
                this.canvas.width = this.video.clientWidth;
                this.canvas.height = this.video.clientHeight;
                this.canvas.style.width = this.video.clientWidth + 'px';
                this.canvas.style.height = this.video.clientHeight + 'px';
                this.draw();
            }

            // 添加视频尺寸变化监听
            setupVideoResizeObserver() {
                const resizeObserver = new ResizeObserver(() => {
                    this.updateCanvasSize();
                });
                resizeObserver.observe(this.video);
            }

            // 获取视频实际显示区域（排除黑边）
            getVideoDisplayRect() {
                const videoRatio = this.video.videoWidth / this.video.videoHeight;
                const containerRatio = this.video.clientWidth / this.video.clientHeight;
                
                let width, height, left, top;
                
                if (containerRatio > videoRatio) {
                    // 上下黑边
                    height = this.video.clientHeight;
                    width = height * videoRatio;
                    left = (this.video.clientWidth - width) / 2;
                    top = 0;
                } else {
                    // 左右黑边
                    width = this.video.clientWidth;
                    height = width / videoRatio;
                    left = 0;
                    top = (this.video.clientHeight - height) / 2;
                }

                return { width, height, left, top };
            }

            // 将比例坐标转换为实际坐标（基于视频实际区域）
            getActualPoint(point) {
                const rect = this.getVideoDisplayRect();
                return {
                    x: rect.left + point.x * rect.width,
                    y: rect.top + point.y * rect.height
                };
            }

            // 将实际坐标转换为比例坐标（基于视频实际区域）
            getRelativePoint(point) {
                const rect = this.getVideoDisplayRect();
                return {
                    x: (point.x - rect.left) / rect.width,
                    y: (point.y - rect.top) / rect.height
                };
            }

            getMousePos(event) {
                const rect = this.canvas.getBoundingClientRect();
                return {
                    x: event.clientX - rect.left,
                    y: event.clientY - rect.top
                };
            }

            checkCornerHit(mousePos) {
                // 将鼠标位置转换为比例坐标
                const relPos = this.getRelativePoint(mousePos);
                // 计算动态阈值比例（基于实际视频区域的最小尺寸）
                const videoRect = this.getVideoDisplayRect();
                const minDimension = Math.min(videoRect.width, videoRect.height);
                const thresholdRatio = this.threshold / minDimension;
                
                for(let i=0; i<this.points.length; i++) {
                    const dx = relPos.x - this.points[i].x;
                    const dy = relPos.y - this.points[i].y;
                    if(Math.sqrt(dx*dx + dy*dy) < thresholdRatio) {
                        return i;
                    }
                }
                return -1;
            }

            handleMouseDown(event) {
                const mousePos = this.getMousePos(event);
                this.selectedCorner = this.checkCornerHit(mousePos);
                
                if(this.selectedCorner > -1) {
                    this.dragType = 'corner';
                    this.isDragging = true;
                } else {
                    // 检测是否在多边形内部（使用比例坐标）
                    const relPos = this.getRelativePoint(mousePos);
                    if(this.isPointInPolygon(relPos)) {
                        this.dragType = 'move';
                        this.isDragging = true;
                        // 记录拖动起始位置
                        this.dragStart = {
                            x: relPos.x,
                            y: relPos.y
                        };
                    }
                }
            }

            handleMouseMove(event) {
                if(!this.isDragging) return;
                
                const mousePos = this.getMousePos(event);
                const relPos = this.getRelativePoint(mousePos);

                // 边界限制（比例坐标0~1）
                relPos.x = Math.max(0, Math.min(relPos.x, 1));
                relPos.y = Math.max(0, Math.min(relPos.y, 1));

                if(this.dragType === 'corner') {
                    this.points[this.selectedCorner] = relPos;
                } else if(this.dragType === 'move') {
                    // 计算移动增量（基于初始拖动位置）
                    const dx = relPos.x - this.dragStart.x;
                    const dy = relPos.y - this.dragStart.y;
                    
                    // 应用增量到所有点
                    const newPoints = this.points.map(p => ({
                        x: p.x + dx,
                        y: p.y + dy
                    }));

                    // 检查边界
                    const isValid = newPoints.every(p => 
                        p.x >= 0 && p.x <= 1 && 
                        p.y >= 0 && p.y <= 1
                    );
                    
                    if(isValid) {
                        this.points = newPoints;
                        // 更新拖动起始位置为当前鼠标位置
                        this.dragStart = {
                            x: relPos.x,
                            y: relPos.y
                        };
                    }
                }
                
                this.draw();
                this.updatePositionDisplay();
            }

            handleMouseUp() {
                this.isDragging = false;
                this.dragType = null;
                this.selectedCorner = -1;
            }

            isPointInPolygon(relPos) {
                // 射线法判断点是否在多边形内（使用比例坐标）
                let inside = false;
                for(let i=0, j=this.points.length-1; i<this.points.length; j=i++) {
                    const xi = this.points[i].x, yi = this.points[i].y;
                    const xj = this.points[j].x, yj = this.points[j].y;
                    
                    const intersect = ((yi > relPos.y) !== (yj > relPos.y)) &&
                        (relPos.x < (xj - xi) * (relPos.y - yi) / (yj - yi) + xi);
                    if(intersect) inside = !inside;
                }
                return inside;
            }

            draw() {
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                
                // 转换比例坐标为实际坐标
                const actualPoints = this.points.map(p => this.getActualPoint(p));
                
                // 绘制半透明填充
                this.ctx.fillStyle = 'rgba(0, 150, 255, 0.2)';
                this.ctx.beginPath();
                this.ctx.moveTo(actualPoints[0].x, actualPoints[0].y);
                actualPoints.slice(1).forEach(p => this.ctx.lineTo(p.x, p.y));
                this.ctx.closePath();
                this.ctx.fill();
                
                // 绘制边框
                this.ctx.strokeStyle = '#2196F3';
                this.ctx.lineWidth = 2;
                this.ctx.stroke();
                
                // 绘制顶点
                actualPoints.forEach(p => {
                    this.ctx.beginPath();
                    this.ctx.arc(p.x, p.y, 4, 0, Math.PI*2);
                    this.ctx.fillStyle = '#FF5722';
                    this.ctx.fill();
                });
            }

            updatePositionDisplay() {
                // 显示实际坐标和比例坐标
                const actualPoints = this.points.map(p => this.getActualPoint(p));
                const positions = actualPoints.map((p, i) => 
                    `${Math.round(p.x)},${Math.round(p.y)} (${this.points[i].x.toFixed(2)},${this.points[i].y.toFixed(2)})`
                ).join(' | ');
                document.getElementById('roiPosition').textContent = positions;
            }
        }

        // 初始化播放器和区域选择（异步加载ROI数据）
        initPlayer();
        let roiSelector;
        
        // 等待视频元数据加载完成
        document.getElementById('videoElement').onloadedmetadata = async () => {
            const selector = new ROISelector();
            await selector.initialize();
            roiSelector = selector;
            console.log('ROI选择器初始化完成');
        };

        // 更新识别区域按钮点击处理
        document.getElementById('updateRoiBtn').addEventListener('click', async () => {
            try {
                const points = roiSelector.points;
                
                // 计算x,y,w,h（基于左上、右上、左下点）
                // 发送四个顶点的比例坐标数组
                    const response = await fetch('/riocfg', {
                        method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        roi_points: points.map(p => ({
                            x: parseFloat(p.x.toFixed(4)),
                            y: parseFloat(p.y.toFixed(4))
                        }))
                    })
                });
                
                const result = await response.json();
                // alert(result.status || '区域更新成功');
            } catch (error) {
                console.error('区域更新失败:', error);
                alert('区域更新请求失败');
            }
        });
    </script>
<div id="dataDisplay" style="position: absolute; left: 20px; top: 20px; font-size: 32px; color: rgb(255, 0, 0);"></div>

<script>
// 创建WebSocket连接
const socket = new WebSocket(`ws://${window.location.hostname}:801`);

// 处理接收到的消息
socket.onmessage = function(event) {
    try {
        const data = JSON.parse(event.data);
        if(data.ytxs) {
            document.getElementById('dataDisplay').textContent = `引体向上统计: ${data.ytxs}`;
        }
    } catch(e) {
        console.error('JSON解析错误:', e);
    }
};

// 错误处理
socket.onerror = function(error) {
    console.error('WebSocket错误:', error);
};

// 连接关闭处理
socket.onclose = function(event) {
    if(event.wasClean) {
        console.log(`连接正常关闭 code=${event.code} reason=${event.reason}`);
    } else {
        console.warn('连接异常断开');
    }
};
</script>
</body>
</html>
